package com.lifang.framework.aop.framework;

import cn.hutool.core.util.ReflectUtil;
import com.lifang.framework.util.AopProxyUtils;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.List;

public class ReflectiveMethodInvocation implements MethodInvocation {

    protected final Object target;

    protected final Method method;

    protected Object[] arguments;

    private final Class<?> targetClass;

    protected List<?> interceptorsAndDynamicMethodMatchers;

    private int currentInterceptorIndex = -1;

    public ReflectiveMethodInvocation(Object target, Method method, Class<?> targetClass, Object[] arguments, List<?> dynamicMethods) {
        this.target = target;
        this.method = method;
        this.targetClass = targetClass;
        this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
        this.interceptorsAndDynamicMethodMatchers = dynamicMethods;
    }

    @Override
    public Method getMethod() {
        return method;
    }

    @Override
    public Object[] getArguments() {
        return arguments;
    }

    @Override
    public Object proceed() throws Throwable {
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinPoint();
        }
        Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            InterceptorAndDynamicMethodMatcher interceptionAdvice = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            if (interceptionAdvice.getMethodMatcher().matches(method, this.targetClass)) {
                return interceptionAdvice.getMethodInterceptor().invoke(this);
            } else {
                return proceed();
            }
        } else {
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

    @Override
    public Object getThis() {
        return target;
    }

    @Override
    public AccessibleObject getStaticPart() {
        return method;
    }

    protected Object invokeJoinPoint() throws Throwable {
        ReflectUtil.setAccessible(method);
        return method.invoke(target, arguments);
    }

    public void setArguments(Object[] arguments) {
        this.arguments = arguments;
    }

    public Object getTarget() {
        return target;
    }
}
